
#include "Graphics.h"

/*----------------------------------------------------------------------------*/
void Graphics::callbackDisplayFrame() { 
	gfx->displayFrame();
}

/*----------------------------------------------------------------------------*/
void Graphics::callbackReshapeWindow(int w, int h) { 
	gfx->reshapeWindow(w,h);
}

/*----------------------------------------------------------------------------*/
void Graphics::callbackKeysSpecialDown(int key, int x, int y) { 
	gfx->keysSpecialDown(key,x,y);
}

/*----------------------------------------------------------------------------*/
void Graphics::callbackKeysSpecialUp(int key, int x, int y) { 
	gfx->keysSpecialUp(key,x,y);
}

/*----------------------------------------------------------------------------*/
Graphics::Graphics(int argc, char *argv[], Sim* pSim) {
	gfx = this;
	sim = pSim;

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(800,800);
	glutCreateWindow("Orbit Model");

	glutDisplayFunc(Graphics::callbackDisplayFrame);
	glutReshapeFunc(Graphics::callbackReshapeWindow);
	glutSpecialFunc(Graphics::callbackKeysSpecialDown);
	glutSpecialUpFunc(Graphics::callbackKeysSpecialUp);

	initialize();

	/*// Make a "real" Windows screen
	setWindowResizeFunc(reshapeWindow);
	setWindowDrawFunc(displayFrame);
	initWindow("Orbit Test With REAL Windows!",800,800,false);*/
}

/*----------------------------------------------------------------------------*/
void Graphics::initialize() {

	followBodyIndex = 1;
	keyRightDown = false;
	keyLeftDown = false;
	camDist = 20.0;
	camAtMoon = false;

	numStars = 2000;
	stars = new Star[numStars];
	
	ones[0] = ones[1] = ones[2] = ones[3] = 1.0;
	mids[0] = mids[1] = mids[2] = 0.5; mids[3] = 1.0;
	zeros[0] = zeros[1] = zeros[2] = 0.0; zeros[3] = 1.0;
	alphas[0] = alphas[1] = alphas[2] = 1.0; alphas[3] = 0.0;
	blues[0] = 0.0; blues[1] = 0.65; blues[2] = blues[3] = 1.0;

	//////////////////////////////////////

	glEnable(GL_LIGHTING);
	glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);

	lightPos[0] = 100;
	lightPos[1] = 150;
	lightPos[2] = 200;
	lightPos[3] = 0;

	glLightfv(GL_LIGHT1, GL_AMBIENT, zeros);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, ones);
	glLightfv(GL_LIGHT1, GL_POSITION, lightPos);
	glLightfv(GL_LIGHT1, GL_SPECULAR, ones );
	glEnable(GL_LIGHT1);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glClearColor(0,0,0,0);

	/*glEnable(GL_FOG);
	float fogColor[3] = {0,0,0};
	glFogi(GL_FOG_MODE, GL_LINEAR);
	glFogf(GL_FOG_START, 2.0);
	glFogf(GL_FOG_END, 200.0);
	glFogfv(GL_FOG_COLOR, fogColor);*/
}

/*----------------------------------------------------------------------------*/
void Graphics::reshapeWindow(int width, int height) {
	if ( !height ) { height = 1; }
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(57.0, width/(float)height, 0.1, 2000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


////////////////////////////////////////////////////////////////////////////////
/*----------------------------------------------------------------------------*/
void Graphics::start() {
	glutMainLoop();
}

/*----------------------------------------------------------------------------*/
void Graphics::displayFrame() {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glLoadIdentity();

	vec3f c = *(sim->getGravBody(0)->getLoc());
	vec3f b = *(sim->getGravBody(followBodyIndex)->getLoc());
	vec3f v = *(sim->getGravBody(followBodyIndex)->getVel());
	followBodyLoc = b;

	if ( camAtMoon ) { vec3f temp = c; c = b; b = temp; }

	vec3f diff = subVec(c,b);
	vec3f up = normalize( cProd(v, diff) );
	diff = addVec( diff, scaleVec(up, 1/camDist*25) );
	diff = scaleVec( normalize(diff), camDist );
	
	eye = addVec( c, diff );
	view = normalize(diff);

	gluLookAt(eye.x,eye.y,eye.z,  b.x,b.y,b.z,  up.x, up.y, up.z);

	glLightfv(GL_LIGHT1, GL_POSITION, lightPos);

	draw();
 
	glutSwapBuffers();
	glutPostRedisplay();
}

/*----------------------------------------------------------------------------*/
void Graphics::draw() {

	drawStars();
	drawGravBodies();
}

/*----------------------------------------------------------------------------*/
void Graphics::drawStars() {

	for ( register unsigned int i = 0 ; i < numStars ; i++ ) {

		glMaterialfv( GL_FRONT, GL_DIFFUSE, stars[i].getDiffuse() );
		glMaterialfv( GL_FRONT, GL_AMBIENT, ones );
		glMaterialfv( GL_FRONT, GL_EMISSION, ones );

		const vec3f* loc = stars[i].getLoc();
		
		glPointSize(stars[i].getSize()); // Must call outside of glBegin/End
		glBegin(GL_POINTS);
			glVertex3f( loc->x, loc->y, loc->z );
		glEnd();
	}
}

/*----------------------------------------------------------------------------*/
void Graphics::drawGravBodies() { 

	GravBody* body;
	vec3f loc;

	for ( int i = 0 ; i < sim->getNumBodies() ; i++ ) {

		body = sim->getGravBody(i);
		loc = (i == followBodyIndex) ? followBodyLoc : body->getGfxLoc();
		float dist = len( subVec(loc, eye) );

		glPushMatrix();

			glTranslatef(loc.x, loc.y, loc.z);
			glMaterialfv( GL_FRONT, GL_AMBIENT, mids );
			glMaterialfv( GL_FRONT, GL_EMISSION, zeros );
			glMaterialfv( GL_FRONT, GL_DIFFUSE, blues);

			float max = body->getMaxDetail();
			float dValue = max - (dist/60.0)*max + 1;
			dValue *= body->getRad();
			int detailLevel = (dValue >= 1 ? floor(dValue) : 1);

			drawGeosphere( body->getGeosphere(), detailLevel );
			drawGeosphereHistory( body, &loc );

		glPopMatrix();
	}
}

/*----------------------------------------------------------------------------*/
void Graphics::drawGeosphere(Geosphere* geo, int detailLevels) {

	glBegin(GL_TRIANGLES);
		for ( int i = 0 ; i < geo->getNumFaces() ; i++ ) {			
			drawGeosphereFace(geo->getFace(i), detailLevels);
		}
	glEnd();
}

/*----------------------------------------------------------------------------*/
void Graphics::drawGeosphereFace(GeosphereFace* face, int levelsLeft) {
	
	if ( face->getIsDivided() && levelsLeft > 0 ) {
		for ( register char i = 0 ; i < 4 ; i++ ) { 
			drawGeosphereFace(face->getFace(i), levelsLeft-1);
		}
		return;
	}

	// Skip backfacing triangles
	if ( dProd(view, *(face->getFaceNorm())) < -0.2 ) { return; }

	for ( register char i = 0 ; i < 3 ; i++ ) {
		const GeosphereVert* v = face->getVert(i);
		glNormal3f(v->norm.x,v->norm.y,v->norm.z);
		glVertex3f(v->vert.x,v->vert.y,v->vert.z);
	}
}

/*----------------------------------------------------------------------------*/
void Graphics::drawGeosphereHistory( GravBody* gravBody, const vec3f* loc ) {
	
	// Use a semaphore to avoid sync exceptions
	gravBody->getHistoryAccess(true);
	list<vec3f> h = *gravBody->getHistory();
	gravBody->getHistoryAccess(false);

	list<vec3f>::iterator i;
	alphas[3] = 1.0;
	glMaterialfv( GL_FRONT, GL_AMBIENT, ones );
	glMaterialfv( GL_FRONT, GL_EMISSION, ones );
	float alphaStep = 1.0/h.size();
	
	glBegin(GL_LINE_STRIP);

		glMaterialfv( GL_FRONT, GL_DIFFUSE, alphas);
		glVertex3f(0,0,0);

		for ( i = h.begin() ; i != h.end() ; ++i ) {
			alphas[3] -= alphaStep;
			glMaterialfv( GL_FRONT, GL_DIFFUSE, alphas);
			vec3f pos = *i;
			glVertex3f(pos.x-loc->x, pos.y-loc->y, pos.z-loc->z);
		}

	glEnd();
}


////////////////////////////////////////////////////////////////////////////////
/*----------------------------------------------------------------------------*/
void Graphics::keysSpecialDown(int key, int x, int y) {	
	if ( key == GLUT_KEY_UP	) {	camDist -= (camDist > 2 ? 0.5 : 0); }
	if ( key == GLUT_KEY_DOWN ) { camDist += 0.5; }
	
	if ( key == GLUT_KEY_RIGHT ) { 
		if ( keyRightDown ) { return; }
		followBodyIndex = (followBodyIndex+1)%(sim->getNumBodies()-1) + 1;
		keyRightDown = true;
	}
	if ( key == GLUT_KEY_LEFT ) {
		if ( keyLeftDown ) { return; }
		if ( --followBodyIndex < 1 ) { followBodyIndex = sim->getNumBodies()-1; }
		keyRightDown = true;
	}
	if ( key == GLUT_KEY_PAGE_UP ) { camAtMoon = false; }
	if ( key == GLUT_KEY_PAGE_DOWN ) { camAtMoon = true; }
}

/*----------------------------------------------------------------------------*/
void Graphics::keysSpecialUp(int key, int x, int y) {
	if ( key == GLUT_KEY_RIGHT ) { keyRightDown = false; }
	if ( key == GLUT_KEY_LEFT ) { keyLeftDown = false; }
}
